package com.wang.tool.util;

import com.wang.tool.monitor.LinkLogUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * multi loader merge page load data
 */
public class MultiplePageUtil {


    /**
     * Merges page load data from multiple loaders.
     * usage: Used when paging loads are performed on multiple data source merges
     * Multiple loader are loaded by sort
     *
     * @param action    the action name for logging
     * @param param     the parameter for loaders
     * @param pageParam the {@link PageParam} containing pageNum and pageSize
     * @param loaders   the list of {@link SubPageLoader} to load data from
     * @param <P>       the type of parameter
     * @param <T>       the type of data
     * @return a {@link PageResult} containing the merged results
     */
    public static <P, T> PageResult<T> multiplePage(String action, P param, PageParam pageParam, List<SubPageLoader<P, T>> loaders) {
        List<T> combinedData = new ArrayList<>();
        if (pageParam == null || pageParam.getPageNum() == null || pageParam.getPageSize() == null || CommonUtil.isEmpty(loaders)) {
            SimpleLogUtil.warn("multiplePage pageParam/loaders is null, action:{}", action);
            return PageResult.of(0, combinedData);
        }
        Integer pageNum = pageParam.getPageNum();
        Integer pageSize = pageParam.getPageSize();

        int totalCount = 0;
        int itemsToSkip = (pageNum - 1) * pageSize;
        int logicLoadedCount = 0;
        // for each loader
        for (SubPageLoader<P, T> loader : loaders) {
            if (loader == null) {
                continue;
            }
            String loaderName = loader.getLoaderName();
            int loaderCount = loader.count(param);
            LinkLogUtil.info("multiple_page_sub_loader_count, action:{0},pageNum:{1}, pageSize:{2}, loader:{3}, loaderCount:{4}, itemsToSkip:{5}, logicLoadedCount:{6}, nowCount:{7}", action, pageNum, pageSize, loaderName, loaderCount, itemsToSkip,logicLoadedCount, combinedData.size());
            if (loaderCount == 0) {
                continue;
            }
//            sum
            totalCount += loaderCount;
            // enough then continue
            if (logicLoadedCount >= pageSize) {
                continue;
            }

            // if skip this loader
            if (itemsToSkip >= loaderCount) {
                itemsToSkip -= loaderCount;
                continue;
            }

            // Pagination starts from itemsToSkip
//            loaderCount = 13,item2ToSkip=7, pageSize=5
//            firstPageNumToLoad = 1
            int firstPageNumToLoad = (itemsToSkip / pageSize) + 1;
            int offsetFirstPage = itemsToSkip % pageSize;

//            this loader need to loadRet
            int logicNeedLoadCount = Math.min(pageSize - logicLoadedCount, loaderCount - itemsToSkip);

            logicLoadedCount += logicNeedLoadCount;

//            first page logic need load
            int firstPageLogicLoadCount = Math.min(Math.min(pageSize - offsetFirstPage, loaderCount - itemsToSkip), logicNeedLoadCount);


//          start load
            itemsToSkip = 0;

//            first page maybe keep the second half
            PageParam nowPage = PageParam.of(firstPageNumToLoad, pageSize);
            List<T> loadRet = loader.pageLoad(param, nowPage);
            LinkLogUtil.info("multiple_page_sub_loader_page_load, action:{0}, pageNum:{1}, pageSize:{2}, loader:{3}, pageLoaderCount:{4}, logicNeedLoadCount:{5}, logicLoadedCount:{6}", action, firstPageNumToLoad, pageSize, loaderName, loadRet == null ? 0 : loadRet.size(), logicNeedLoadCount, logicLoadedCount);
            if (loadRet == null || loadRet.isEmpty() || loadRet.size() <= offsetFirstPage) {
                LinkLogUtil.warn("multiple_page_sub_load_need_load_not_enough, action:{0}, loader:{1} , pageLoaderCount:{2}, offsetFirstPage:{3}", action, loaderName, loadRet == null ? 0 : loadRet.size(), offsetFirstPage);
            } else {
                int end = Math.min(loadRet.size(), offsetFirstPage + logicNeedLoadCount);
                if (end - offsetFirstPage < firstPageLogicLoadCount) {
                    LinkLogUtil.warn("multiple_page_sub_load_need_load_not_enough, action:{0}, loader:{1} , firstPageLoadCount:{2}, firstPageLogicLoadCount:{3}", action, loaderName, end - offsetFirstPage, firstPageLogicLoadCount);
                }
                List<T> toAddData = loadRet.subList(offsetFirstPage, end);
                combinedData.addAll(toAddData);
            }

//            second page
            logicNeedLoadCount -= firstPageLogicLoadCount;
            // Check if more data is needed
            if (logicNeedLoadCount > 0 && offsetFirstPage > 0) {
                int secondPageToLoad = firstPageNumToLoad + 1;
                nowPage = PageParam.of(secondPageToLoad, pageSize);
                loadRet = loader.pageLoad(param, nowPage);
                LinkLogUtil.info("multiple_page_sub_load, action:{0}, pageNum:{1}, pageSize:{2}, loader:{3}, pageLoaderCount:{4}, needLoadCount:{5}, logicLoadedCount:{6}", action, secondPageToLoad, pageSize, loaderName, loadRet == null ? 0 : loadRet.size(), logicNeedLoadCount, logicLoadedCount);
                if (loadRet == null || loadRet.isEmpty()) {
                    LinkLogUtil.warn("multiple_page_sub_load_need_load_not_enough, action:{0}, loader:{1} , secondPageLoadCount:{2}, secondPageLogicLoadCount:{3}", action, loaderName, 0, logicNeedLoadCount);
                    continue;
                }
                if (loadRet.size() < logicNeedLoadCount) {
                    LinkLogUtil.warn("multiple_page_sub_load_need_load_not_enough, action:{0}, loader:{1} , secondPageLoadCount:{2}, secondPageLogicLoadCount:{3}", action, loaderName, loadRet.size(), logicNeedLoadCount);
                }
                List<T> secondAddData = loadRet.subList(0, Math.min(loadRet.size(), logicNeedLoadCount));
                combinedData.addAll(secondAddData);
            }
        }
        return PageResult.of(totalCount, combinedData);
    }


    /**
     * @param <P>
     * @param <T>
     * @see MultiplePageUtil#multiplePage(String, Object, PageParam, List)
     */
    public static interface SubPageLoader<P, T> {

        /**
         * loaderName
         * suggest unique in once load
         *
         * @return
         */
        String getLoaderName();

        /**
         * get count of all
         *
         * @param params
         * @return
         */
        int count(P params);

        /**
         * page load
         *
         * @param params
         * @param pageParam Not null
         * @return
         */
        List<T> pageLoad(P params, PageParam pageParam);
    }
}
